//
//	IOBUS - packet transmission / reception
//
//  (c) 2008 mocom software GmbH & Co KG
//
//  $Author: volker $
//  $Date: 2009-10-01 18:51:54 +0200 (Do, 01. Okt 2009) $
//  $Revision: 509 $
//
//-------------------------------------------------------------------------------------------
//
//
//	PACKET STRUCTURE
//
//	+------+-----+-----+------+--------------+
//	| ADDR | CMD | CYL | CRC8 | two-char-gap |
//	+------+-----+-----+------+--------------+
//
//	+------+-----+-----+------+-----+-------------+-------+--------------+
//	| ADDR | CMD | CYL | CRC8 | LEN | 0..127 DATA | crc16 | two-char-gap |
//	+------+-----+-----+------+-----+-------------+-------+--------------+
//
//	UNICAST/MULTICAST I/O POROTOKOLL
//
//	Properties:
//
//	Address:	bit7 = 1
//				bit6 = 1:	MASTER TO SLAVE (to ease spying)
//				bit6 = 0:	SLAVE TO MASTER (to ease spying)
//				0..31:		SLAVE ADDRESS
//				0xff:		BROADCAST
//				0x00:		RESERVED
//
//	Cmd:		  7    6     5..0
//				+---+------+-----+
//				| 0 | DATA | CMD |
//				+---+------+-----+
//
//				ERR RESP:
//
//				  7    6     5..0
//				+---+------+-------+
//				| 0 | DATA |  ERR  |
//				+---+------+-------+
//
//	Cycle:		bit7 = 0
//				0..127		sequence id
//							Master issues cycle id, slave respondes without
//							change.
//
//	crc8:		LOBYTE(CRC16)
//
//	Len:		bit7 = 0
//				0..127		packet len in bytes-1. Optional by bit 6 of cmd.
//
//	Data:		[Len] 		byte data per packet.0 = 128
//
//	crc:		CRC16
//

#include <iobus.h>

typedef struct IOBUS_proto_data
{
	unsigned 		state;			// rx state
	IOBusStatistics	statistics;		//
	// transmit buffer
	IOBusFrame		tx;
	// receive buffer
	IOBusFrame		rx;
	unsigned		rxidx;
	UINT16			crc16;
	UINT8 			nodeaddr;
	INT8			flag_master;
	INT8			flag_data;
} IOBusProtocol;


static IOBusProtocol context;

#define IOBUS_MASKBIT8(x)	((x) & 0x7f)
#define IOBUS_SETBIT8(x)	((x) | 0x80)

void IOBusInitLinkLayer( UINT8 node )
{
	IOBusHalMemorySet( &context, 0, sizeof( context ) );
	if ( node <= IO_ADDR_Mask )
	{
		context.flag_master = false;
		context.nodeaddr = node;
	}
	else
	{
		context.flag_master = true;
		context.nodeaddr = 0xff;
	}
}

IOBusStatistics *IOBusGetStatisticsPtr()
{
	return &context.statistics;
}

int IOBusSendCmd( UINT8 node, UINT8 cmdres, UINT8 cycle, UINT8 len, UINT8 *data )
{
	unsigned idx;
	unsigned crc16 = 0xffff;
	unsigned txlen;

	if ( len > IOBUS_MAX_DATA_LENGTH )
		return IO_ERR_Len;

	if ( node != IO_ADDR_Broadcast )
	{
		node &= IO_ADDR_Mask;
		if ( context.flag_master )
		{
			node |= IO_ADDR_Master2Slave;
		}
		else
		{
			node |= IO_ADDR_Slave2Master;
		}
	}

	// header
	context.tx.addr = IOBUS_SETBIT8( node );
	context.tx.cycle  = IOBUS_MASKBIT8( cycle );

	if ( len && data )
	{
		// complete header
		context.tx.cmdres = IOBUS_MASKBIT8( cmdres | IO_DATA_Yes );
		crc16 = CRC16Block( &context.tx.addr, 3, crc16 );
		context.tx.crc    = crc16 & 0xff;

		// length
		context.tx.len    = IOBUS_MASKBIT8( len - 1 );
		crc16 = CRC16Step( context.tx.len, crc16 );

		// Data Frame
		for ( idx = 0;idx < len;idx++ )
		{
			context.tx.data[idx] = *data++;
		}

		// data frame CRC
		crc16 = CRC16Block( context.tx.data, len, crc16 );
		context.tx.data[idx++] = crc16 & 0xff;
		context.tx.data[idx++] = ( crc16 >> 8 ) & 0xff;
		txlen = IOBUS_MIN_FRAME_LENGTH + 1 + len + 2;
	}
	else
	{
		len = 0;
		// control frame
		context.tx.cmdres = IOBUS_MASKBIT8( cmdres | IO_DATA_No );
		context.tx.cycle  = IOBUS_MASKBIT8( cycle );
		crc16 = CRC16Block( &context.tx.addr, 3, crc16 );
		context.tx.crc    = IOBUS_MASKBIT8( crc16 & 0xff );
		txlen = IOBUS_MIN_FRAME_LENGTH;
	}

	IOBusHalSendData( &context.tx.addr, txlen );

	return IO_ERR_Ok;
}

//
//	IOBUs Packet Assembler States
//
enum
{
	sHUNT,	// wait for a gap
	sIDLE,	// waiting for address
	sCMD,	// waiting for command
	sCYL,	// waiting for cycle
	sCRC8,	// waiting for 8-Bit CRC, final state for short messages
	sLEN,	// waiting for LEN, instead of CRC8
	sDATA,	// waiting for LEN+1 data bytes
	sCRC16_LSB,	// waiting for LSB of CRC16
	sCRC16_MSB	// waiting for MSB of CRC16
};

void IOBusRxAssemble( unsigned chr, int error )
{
	// framing or other UART error or gap detetced
	if ( IOBusHalGapDetected( context.state == sHUNT ) || error )
	{
		// if we got a gap or error in the middle of an assembly process
		// its a failture. We use the "aborted" counter to trace this.
		if ( context.state != sHUNT )
		{
			context.statistics.aborted++;
			context.state = sHUNT;
		}
		else if ( context.state == sHUNT && error )
		{
			// if we are in hunt and get an error, we
			// have a false start
			context.statistics.sync_error++;
		}
		else
		{
			context.state = sIDLE;
		}
	}

	switch ( context.state )
	{
		case sHUNT:
			// do nothing until we detected a gap
			break;

		case sIDLE:
			context.statistics.rxstarted++;
			if ( context.flag_master )
			{
				if (( chr & IO_ADDR_Ctrl_Mask ) == IO_ADDR_Slave2Master )
				{
					context.state = sCMD;
					context.rx.addr = chr & IO_ADDR_Mask;
					context.crc16 = CRC16Step( chr, 0xffff );
				}
				else
				{
					context.statistics.coding_error++;
					context.state = sHUNT;
				}
			}
			else
			{
				if ( chr == ( IO_ADDR_Master2Slave | context.nodeaddr ) || chr == IO_ADDR_Broadcast )
				{
					context.state = sCMD;
					context.rx.addr = chr;
					context.crc16 = CRC16Step( chr, 0xffff );
				}
				else
				{
					context.statistics.address_mismatch++;
					context.state = sHUNT;
				}
			}
			break;

		case sCMD:
			if (( chr & 0x80 ) == 0x00 )
			{
				context.state = sCYL;
				context.rx.cmdres = chr & IO_CMDRES_Mask;
				context.crc16 = CRC16Step( chr, context.crc16 );
				context.flag_data = ( chr & IO_DATA_Yes ) == IO_DATA_Yes;
			}
			else
			{
				context.statistics.coding_error++;
				context.state = sHUNT;
			}
			break;

		case sCYL:
			if (( chr & 0x80 ) == 0x00 )
			{
				context.state = sCRC8;
				context.rx.cycle = chr;
				context.crc16 = CRC16Step( chr, context.crc16 );
			}
			else
			{
				context.statistics.coding_error++;
				context.state = sHUNT;
			}
			break;

		case sCRC8:
			context.rx.len = 0;
			if (( context.crc16 & 0xff ) == chr )
			{
				if ( context.flag_data )
				{
					context.state = sLEN;
				}
				else
				{
					IOBusIndicate( &context.rx );
					context.state = sHUNT;
				}
			}
			else
			{
				context.statistics.crc_error++;
				context.state = sHUNT;
			}
			break;

		case sLEN:
			if (( chr & 0x80 ) == 0 )
			{
				context.rx.len = chr + 1;
				context.crc16 = CRC16Step( chr, context.crc16 );
				if ( context.rx.len > IOBUS_MAX_DATA_LENGTH )
				{
					context.statistics.len_error++;
					context.state = sHUNT;
				}
				else
				{
					context.rxidx = 0;
					context.state = sDATA;
				}
			}
			else
			{
				context.statistics.coding_error++;
				context.state = sHUNT;
			}
			break;

		case sDATA:
			context.rx.data[context.rxidx++] = chr;
			context.crc16 = CRC16Step( chr, context.crc16 );
			if ( context.rx.len == context.rxidx )
			{
				context.state = sCRC16_LSB;
			}
			break;

		case sCRC16_LSB:
			if (( context.crc16 & 0xff ) == ( chr & 0xff ) )
			{
				context.state = sCRC16_MSB;
			}
			else
			{
				context.statistics.crc_error++;
				context.state = sHUNT;
			}
			break;

		case sCRC16_MSB:
			if (( context.crc16 >> 8 ) == ( chr & 0xff ) )
			{
				IOBusIndicate( &context.rx );
				context.state = sHUNT;
			}
			else
			{
				context.statistics.crc_error++;
			}
		default:
			context.state = sHUNT;
	}
}
